with Ada.Strings; use Ada.Strings;
with Ada.Strings.Unbounded;
with Legend.Roles;
with Legend.Settings;
with Legend.Grps;
with Legend.StrRes;
with Legend.Resources;
with Legend.Dialogs;
with Legend.Traces;
with Legend.Items_Dialog;
with Legend.Hero_State_Dialog;
with Legend.Colors;
with Legend.Music;
package body Legend.Stages.WarFields is
   use type Legend.Roles.Role_Pointer;

   package Context_Menus is

      CMD_Move     : constant := 1;
      CMD_Cast     : constant := CMD_Move + 1;
      CMD_Poison   : constant := CMD_Cast + 1;
      CMD_Detoxify : constant := CMD_Poison + 1;
      CMD_Cure     : constant := CMD_Detoxify + 1;
      CMD_Item     : constant := CMD_Cure + 1;
      CMD_Wait     : constant := CMD_Item + 1;
      CMD_State    : constant := CMD_Wait + 1;
      CMD_Rest     : constant := CMD_State + 1;
      CMD_Auto     : constant := CMD_Rest + 1;

   end Context_Menus;

   procedure Load_Map (this : in out War_Field_Type)
   is
      reader : Grps.Group_File_Type;
   begin
      reader.Open(JY_Path & "warfld.idx", JY_Path & "warfld.grp");
      reader.Set_Block_Index(this.Profile.Scene);
      -- 战斗地图只有两层
      for layer of this.Layers(ml_Earth .. ml_Surface) loop
         layer := new War_Layer_Type'(others => (others => 0));
         Layer_Type'Read(reader.Data_Stream, layer.all);
      end loop;
      reader.Close;
      this.Hit_Surface := True;
      this.Visibles := (ml_Earth | ml_Surface => True, others => False);
      if Resources.Attack_Effects = null then
         Resources.Attack_Effects := Resources.Load_Pictures("EFT.IDX", "EFT.GRP");
      end if;
   end Load_Map;

   procedure Load_Heroes (this : in out War_Field_Type)
   is
      function Hero_Count return Count_Type 
      is
         result : Count_Type := 0;
      begin
         for h of this.Profile.Heroes.Id loop
            if h /= No_Hero then
               result := result + 1;
            end if;
         end loop;
         for h of this.Profile.Enemies.Id loop
            if h /= No_Hero then
               result := result + 1;
            end if;
         end loop;
         return result;
      end Hero_Count;

      procedure Load_Hero (
         who : in out Roles.Role_Type'Class; 
         member_index : Positive
         )
      is
         member_id : Uint16_t := this.Profile.Heroes.Id(member_index);
         prototype : Hero_Type renames All_World.Heroes(member_id);
      begin
         this.States.Include(member_id, (Camp => k_Player, Exp => 0, others => <>));
         who.Setup(this'Unrestricted_Access, prototype'Unchecked_Access);
         who.Position(
            Position => (
               this.Profile.Heroes.X(member_index), 
               this.Profile.Heroes.Y(member_index)));
         -- 战斗时没有走动动作, 每个方向只有一帧图像
         who.Horse_Movies(
            Movies => (
               others => (
                  Frame_Entry => 2553 + prototype.Icon * 4, 
                  Frame_Count => 1)));
         who.Speed(Settings.Player.Walk.Speed);
         who.Horse(No_Horse);
         who.Face(West);
      end Load_Hero;

      procedure Load_Enemy (
         who : in out Roles.Role_Type'Class;
         member_index : Positive
         )
      is
         member_id : Uint16_t := this.Profile.Enemies.Id(member_index);
         prototype : Hero_Type renames All_World.Heroes(member_id);
      begin
         Reset_State(prototype);
         this.States.Include(member_id, (k_Enemy, Exp => 0, others => <>));
         who.Setup(this'Unrestricted_Access, prototype'Unchecked_Access);
         who.Position(
            Position => (
               this.Profile.Enemies.X(member_index), 
               this.Profile.Enemies.Y(member_index)));
         who.Horse_Movies(
            Movies => (
               others => (
                  Frame_Entry => 2553 + prototype.Icon * 4, 
                  Frame_Count => 1)));
         who.Speed(Settings.NPC.Walk.Speed);
         who.Horse(No_Horse);
         who.Face(East);
      end Load_Enemy;

      heroes : Roles.Roles_Access;
      hero : Roles.Role_Pointer;
   begin
      heroes := new Roles.Roles_Type(Hero_Count);
      Trace_Log(Stdout_GBK("参战英雄总数: ") & heroes.Capacity'Img);
      for i in 1 .. this.Profile.Heroes.Count loop
         if this.Profile.Heroes.Id(i) /= No_Hero then
            hero := new Roles.Role_Type;
            Load_Hero(hero.all, i);
            heroes.Append(hero);
         end if;
      end loop;
      for i in 1 .. this.Profile.Enemies.Count loop
         if this.Profile.Enemies.Id(i) /= No_Hero then
            hero := new Roles.Role_Type;
            Load_Enemy(hero.all, i);
            heroes.Append(hero);
         end if;
      end loop;
      Turns.Sort(heroes.all);
      Roles.Dispose(this.Heroes);
      this.Heroes := heroes;
      for h of this.Heroes.all loop
         Resources.Load_Fight_Picture(h.Prototype.Icon);
      end loop;
   end Load_Heroes;

   procedure Set_Profile (
      this : in out War_Field_Type; 
      Profile : War_Define_Type
      )
   is
   begin
      this.Profile := Profile;
   end Set_Profile;

   function Get_Winner (
      this : War_Field_Type
      ) return Camp_Type
   is
      pragma Inline(Get_Winner);
   begin
      return this.Winner;
   end Get_Winner;

   procedure Paint_Surface (
      this : in out War_Field_Type;
      Corners : View_Corners_Type
      )
   is
      v, c : Point_Type;
   begin
      -- paint shadows
      if this.Shadows.Active then
         for p of this.Shadows.Points loop
            this.Map_To_View(p, v);
            this.View_To_Canvas(v, c);
            this.Canvas.Draw_Picture(this.Shadows.Picture, c);
         end loop;
      end if;
      -- paint locator
      if this.Locator.Active then
         this.Map_To_View(this.Locator.Position, v);
         this.View_To_Canvas(v, c);
         this.Canvas.Draw_Picture(this.Locator.Picture, c);
      end if;
      -- inherited paint buildings
      Stage_Type(this).Paint_Surface(Corners);
      -- paint effects
      if this.Effects.Active then
         for p of this.Effects.Points loop
            this.Map_To_View(p, v);
            this.View_To_Canvas(v, c);
            this.Canvas.Draw_Movie(this.Effects.Movie, c);
         end loop;
      end if;
   end Paint_Surface;

   procedure Paint_Components (this : in out War_Field_Type)
   is
   begin
      this.Box.Visible := not this.Heroes.Is_Empty;
      if this.Box.Visible then
         if this.Hero.Prototype.Id /= this.Box.Hero then
            this.Box.Hero := this.Hero.Prototype.Id;
            this.Box.Paint;
         end if;
      end if;
      Stage_Type(this).Paint_Components;
   end Paint_Components;


   procedure On_Timer (
      this          : in out War_Field_Type;
      Timer         : Hash_Type;
      Count         : Natural;
      Cancel_Bubble : in out Boolean
      )
   is
   begin
      if Timer = this.Effects.Movie.Timer then
         Next_Frame(this.Effects.Movie);
         this.Canvas.Invalidate;
         if Count = this.Effects.Movie.Total then
            this.Effects.Movie.Timer := 0;
            this.Effects.Active := False;
         end if;
      else
         Stage_Type(this).On_Timer(Timer, Count, Cancel_Bubble);
      end if;
   end On_Timer;

   procedure Generate_Movement_Grids (
      this   : War_Field_Type;
      Points : in out Point_Lists.List;
      From   : Point_Type;
      Steps  : Int16_t
      )
   is
      Not_Visited : constant := 0;
      Not_Good    : constant := 16#FFFF#;
      map   : War_Layer_Type := (others => (others => 0));
      stack : Point_Lists.List;
   begin
      Points.Clear;
      Points.Append(From);
      if Steps = 0 then
         return;
      end if;
      map(From.y, From.x) := Uint16_t(Steps + 1);
      stack.Append(From);
      while not stack.Is_Empty loop
         declare
            p : Point_Type := stack.Last_Element;
            q : Point_Type;
         begin
            stack.Delete_Last;
            for f in Face_Type loop
               q := p + Face_To_Offset(f);
               if q.y in map'Range(1) and then q.x in map'Range(2) then 
                  if map(q.y, q.x) = Not_Visited then
                     if this.Has_Obstacle_At(q, No_Horse) then
                        map(q.y, q.x) := Not_Good;
                     else
                        map(q.y, q.x) := map(p.y, p.x) - 1;
                        Points.Append(q);
                        if map(q.y, q.x) > 1 then
                           stack.Append(q);
                        end if;
                     end if;
                  end if;
               end if;
            end loop;
         end;
      end loop;
   end Generate_Movement_Grids;

   procedure Reset_Shadows (
      this            : in out War_Field_Type;
      Center          : Point_Type;
      Radius          : Int16_t;
      Enable_Obstacle : Boolean
      )
   is
   begin
      this.Locator.Position := Center;
      if Enable_Obstacle then
         Generate_Movement_Grids(this.Shadows.Points, Center, Radius);
      else
         Generate_Movement_Grids(this, this.Shadows.Points, Center, Radius);
      end if;
      this.Canvas.Invalidate;
      this.Canvas.Repaint;
   end Reset_Shadows;

   function Current_Hero_State (
      this : in out War_Field_Type
      ) return Role_State_Maps.Reference_Type
   is
   begin
      return this.States.Reference(this.Hero.Prototype.Id);
   end Current_Hero_State;

   procedure On_Player_Turn (this : in out War_Field_Type)
   is
      who : Role_State_Maps.Reference_Type := this.Current_Hero_State;
      hero : Hero_Type renames this.Hero.Prototype.all;
   begin
      if this.Host then
         this.Auto_Fight;
         return;
      end if;
      while who.Can_Action and this.Winner = k_Neutral loop
         this.Canvas.Invalidate;
         this.Canvas.Repaint;
         -- this.Menubar.Show_Modal;
         this.Popup_Menu;
      end loop;
   end On_Player_Turn;

   procedure On_Command (
      this : in out War_Field_Type;
      Command : Integer
      ) 
   is
   begin
      case Command is
         when Context_Menus.CMD_Move =>
            this.On_War_Menu_Move;
         when Context_Menus.CMD_Cast =>
            this.On_War_Menu_Cast;
         when Context_Menus.CMD_Poison =>
            this.On_War_Menu_Poison;
         when Context_Menus.CMD_Detoxify =>
            this.On_War_Menu_Detoxify;
         when Context_Menus.CMD_Cure  =>
            this.On_War_Menu_Cure;
         when Context_Menus.CMD_Item =>
            this.On_War_Menu_Item;
         when Context_Menus.CMD_Wait =>
            this.Winner := k_Enemy;
         when Context_Menus.CMD_State =>
            this.On_War_Menu_State;
         when Context_Menus.CMD_Rest =>
            this.End_Turn(wa_Rest);
         when Context_Menus.CMD_Auto =>
            this.Host := True;
            this.Auto_Fight;
         when others =>
            null;
      end case;
   end On_Command; 

   function Is_Same_Camp (
      this : War_Field_Type;
      R1, R2 : Roles.Role_Pointer
      ) return Boolean
   is
      pragma Inline(Is_Same_Camp);
      h1 : Uint16_t renames R1.Prototype.Id;
      h2 : Uint16_t renames R2.Prototype.Id;
   begin
      return this.States(h1).Camp = this.States(h2).Camp;
   end Is_Same_Camp;

   function Next_Turn (this : in out War_Field_Type) return Boolean
   is
      who : War_State_Ref := this.Current_Hero_State;
   begin
      this.Init_Turn;
      this.Hero.Focus;
      this.Canvas.Repaint;
      case who.Camp is
         when k_Player =>
            this.On_Player_Turn;
         when k_Enemy =>
            this.Auto_Fight;
         when others =>
            null;
      end case;
      if this.Host then
         -- wait for player cancel host
         declare
            use SDL.Keysym, Interactives;
         begin
            case Wait_Key_Or_Timeout(0.5, k_Space & k_Return) is
               when k_Unknown => null;
               when others    => 
                  this.Host := False;
                  Trace_Log("abort host");
            end case;
         end;
      end if;
      if (for all h of this.Heroes.all => this.Is_Same_Camp(h, this.Hero)) then
         this.Winner := who.Camp;
      end if;
      return this.Winner = k_Neutral;
   end Next_Turn;

   procedure Compute_Effect_Grids (
      Plan   : Action_Plan_Type;
      Points : in out Point_Lists.List
      )
   is
      Kongfu : Kongfu_Type renames All_World.Kongfus(Plan.Kongfu);
   begin
      Points.Clear;
      case Kongfu.Range_Type is
         when ar_Point =>
            Points.Append(Plan.Target);
         when ar_Line =>
            declare
               p        : Point_Type := Plan.Target;
               delta_xy : Point_Type := Face_To_Offset(Plan.Face);
            begin
               for i in 1 .. Kongfu.Cast_Range(Plan.Level) loop
                  p := p + delta_xy;
                  Points.Append(p);
               end loop;
            end;
         when ar_Cross =>
            for f in Face_Type loop
               declare
                  p        : Point_Type := Plan.Target;
                  delta_xy : Point_Type := Face_To_Offset(f);
               begin
                  for i in 1 .. Kongfu.Cast_Range(Plan.Level) loop
                     p := p + delta_xy;
                     Points.Append(p);
                  end loop;
                  -- Trace_Log("cross attack points length => " & Points.Length'Img);
               end;
            end loop;
         when ar_Area =>
            if Plan.Role /= null then
               Points.Append(Plan.Target);
            else
               Generate_Area_Attack_Grids(Points, Plan.Target, Kongfu.Damage_Range(Plan.Level));
            end if;
      end case;
   end Compute_Effect_Grids;

   procedure Compute_Best_Area_Attack_Point (
      this          : in out War_Field_Type;
      Attacker      : Roles.Role_Pointer;
      Cast_Radius   : Int16_t;
      Damage_Radius : Int16_t;
      Result        : out Point_Type
      )
   is
      state : War_State_Ref := this.States.Reference(Attacker.Prototype.Id);
      points, area : Point_Lists.List;
      max_enemy_count : Count_Type := 0;
      best_loc : Point_Type := Attacker.Position;
   begin
      Generate_Movement_Grids(points, (0, 0), Cast_Radius);
      for p of points loop
         area.Clear;
         declare
            cast_loc : Point_Type := Attacker.Position + p;
            enemy_count : Count_Type;
         begin
            Generate_Area_Attack_Grids(area, cast_loc, Damage_Radius);
            enemy_count := this.Compute_Area_Hero_Count(area, state.Camp);
            if enemy_count > max_enemy_count then
               max_enemy_count := enemy_count;
               best_loc := cast_loc;
            end if;
         end;
      end loop;
      Result := best_loc;
   end Compute_Best_Area_Attack_Point;

   function Compute_Area_Hero_Count (
      this : War_Field_Type;
      Area : Point_Lists.List;
      Camp : Camp_Type
      ) return Count_Type
   is
      Result : Count_Type := 0;
   begin
      for h of this.Heroes.all loop
         if this.States(h.Prototype.Id).Camp /= Camp then
            if Area.Contains(h.Position) then
               Result := Result + 1;
            end if;
         end if;
      end loop;
      return Result;
   end Compute_Area_Hero_Count;

   procedure Update_Camp_Knowdges (this : in out War_Field_Type)
   is
   begin
      this.Knowdge := (others => 0);
      for h of this.Heroes.all loop
         declare
            camp : Camp_Type := this.States(h.Prototype.Id).Camp;
            hero : Hero_Type renames h.Prototype.all;
         begin
            if hero.Knowdge > this.Knowdge(camp) then
               this.Knowdge(camp) := hero.Knowdge;
            end if;
         end;
      end loop;
   end Update_Camp_Knowdges;

   -- 战斗时经验增长方法
   -- 战场上每次攻击行动可得2点经验值
   -- 对敌造成伤害可得伤害值的1/5的额外经验经验
   -- 杀敌可得敌人等级*10的额外经验奖励
   -- 战斗结束，存活者分享战场预设的经验值
   -- 战斗获得的总经验用于升级
   -- 然后再用所得总经验的80%用于修习物品
   procedure Inc_Exp (
      this : in out War_Field_Type;
      Hero : in out Hero_Type;
      Exp  : Uint16_t
      )
   is
      hero_name : UTF8_String := To_UTF8_Name(Hero.Name);

      procedure Study (Book : Item_Type; Study_Exp : Uint16_t)
      is
         book_name : UTF8_String := To_UTF8_Name(Book.Name);

         function Make (Product_Exp : Uint16_t) return Boolean
         is
            product_id : Uint16_t := Study_Make(Hero, Book, Product_Exp);
         begin
            if product_id = No_Item then
               return False;
            end if;
            declare
               item : Item_Type renames All_World.Items(product_id);
               item_name : UTF8_String := To_UTF8_Name(item.Name);
            begin
               this.Say(hero_name & StrRes.Product & item_name);
            end;
            return True;
         end Make;
      begin
         if Book.Kongfu /= No_Kongfu then
            if Study_Kongfu(Hero, Book, Study_Exp) then
               this.Say(hero_name & StrRes.Study & book_name & StrRes.Success);
            end if;
         else
            if Study_Skill(Hero, Book, Study_Exp) then
               this.Say(hero_name & StrRes.Study & book_name & StrRes.Success);
            end if;
            if Book.Require_Material /= No_Item then
               declare
                  remain_exp : Uint16_t := Study_Exp;
               begin
                  loop
                     exit when not Make(remain_exp);
                     remain_exp := remain_exp - Book.Product_Exp;
                  end loop;
               end;
            end if;
         end if;
      end Study;
   begin
      declare
         old_level : Uint16_t := Hero.Level;
      begin
         Level_Up(Hero, Exp);
         if Hero.Level /= old_level then
            this.Say(hero_name & StrRes.Levelup_Hint);
         end if;
      end;
      if Hero.Learning_Item /= No_Item then
         declare
            Book : Item_Type renames All_World.Items(Hero.Learning_Item);
         begin
            case Book.Kind is
               when ik_Study_Book => Study(Book, Exp * 8 / 10);
               when others        => null;
            end case;
         end;
      end if;
   end Inc_Exp;

   procedure Delete_Dead_Heroes (this : in out War_Field_Type)
   is
      n : Count_Type := 0;
      i, j : Roles.Role_Lists.Cursor := this.Heroes.First;
      h : Roles.Role_Pointer;
   begin
      while Roles.Role_Lists.Has_Element(i) loop
         j := Roles.Role_Lists.Next(i);
         h := Roles.Role_Lists.Element(i);
         if h.Prototype.HP.Now = 0 then
            h.Hide;
            Roles.Dispose(h);
            this.Heroes.Delete(i);
            n := n + 1;
         end if;
         i := j;
      end loop;
      if n > 0 then
         this.Canvas.Invalidate;
         this.Canvas.Repaint;
      end if;
   end Delete_Dead_Heroes;

   procedure Play_Plan_Movie (
      this   : in out War_Field_Type;
      Kind   : Attack_Kind_Type;
      Face   : Face_Type;
      Effect : Uint16_t
      )
   is
      use Ada.Calendar;
      attacker : Hero_Type renames this.Hero.Prototype.all;
      m : Movie_Type;
      movie_end_time : Time;
      movie_dur : Duration := 0.0;
      function Over return Boolean
      is
      begin
         return Clock > movie_end_time;
      end Over;
   begin
      Trace_Log("play effect movie : eft.grp index => " & Effect'Img);
      this.Hero.Face(Face);
      if Get_Attack_Movie(attacker, Kind, Face, m) then
         this.Hero.Play(m, Count => 1);
         movie_dur := Length(m);
      end if;
      if Effect /= No_Effect then
         this.Effects.Movie := Get_Effect_Movie(Effect);
         this.Effects.Active := True;
         Interactives.Start_Play_Movie(this.Effects.Movie);
         movie_dur := Duration'Max(movie_dur, Length(this.Effects.Movie));
      end if;
      movie_end_time := Clock + movie_dur;
      this.Switch_To_Movie_Mode(Over'Access);
      this.Effects.Active := False;
      this.Hero.Stand;
      this.Canvas.Repaint;
   end Play_Plan_Movie;

   procedure Execute_Kongfu_Plan (
      this : in out War_Field_Type;
      Plan : Action_Plan_Type
      )
   is
      Kongfu : Kongfu_Type renames All_World.Kongfus(Plan.Kongfu);
      Attacker : Hero_Type renames this.Hero.Prototype.all;
      Exp : Uint16_t renames this.Current_Hero_State.Exp;
      A_Camp : Camp_Type := this.Current_Hero_State.Camp;
      D_Camp : Camp_Type := not A_Camp;
      Damage : Int16_t;
   begin
      Trace_Log("cast " & To_UTF8_Name(Kongfu.Name));
--      Music.Play_Effect_Sound(Kongfu.Movie_Effect);
      Compute_Effect_Grids(Plan, this.Effects.Points);
      for i in 0 .. Attacker.Attack_Addition_Times loop
         Music.Play_Attack_Sound(Kongfu.Attack_Sound);
         this.Play_Plan_Movie(Kongfu.Attack_Kind, Plan.Face, Kongfu.Movie_Effect);
         Add(Attacker.MP, -(Kongfu.MP_Consume * Int16_t(Plan.Level)));
         Exp := Exp + 2;
         declare
            cur_exp   : Int16_t renames Attacker.Kongfu_Exp(Plan.Index);
            old_exp   : Int16_t := cur_exp;
            delta_exp : Int16_t := Random_Int16(Rnd_Generator, 1, 2);
            cur_level : Int16_t;
            old_level : Int16_t;
         begin
            Add(cur_exp, delta_exp, Points_Range);
            cur_level := cur_exp / Kongfu_Exp_Per_Level;
            old_level := old_exp / Kongfu_Exp_Per_Level;
            if cur_level /= old_level then
               this.Hero.Emit(
                  To_UTF8_Name(Attacker.Name) & 
                  To_UTF8_Name(Kongfu.Name) & 
                  StrRes.Levelup_To & 
                  Int16_t'Image(cur_level + 1) & StrRes.Level,
                  Colors.White);
            end if;
         end;
         for h of this.Heroes.all loop
            if this.Effects.Points.Contains(h.Position) then
               if not this.Is_Same_Camp(this.Hero, h) then
                  Compute_Kongfu_Damage(
                     Attacker, 
                     h.Prototype.all, 
                     Kongfu, 
                     Plan.Level,
                     this.Knowdge(A_Camp),
                     this.Knowdge(D_Camp),
                     Damage);
                  Exp := Exp + Uint16_t(abs(Damage) / 5);
                  if h.Prototype.HP.Now = 0 then
                     Exp := Exp + h.Prototype.Level * 10;
                  end if;
                  h.Emit(Damage'Img, (if Kongfu.Damage_Type = kd_HP then Colors.Red else Colors.Purple));
               end if;
            end if;
         end loop;
      end loop;
   end Execute_Kongfu_Plan;

   procedure Execute_Throw_Plan (
      this : in out War_Field_Type;
      Plan : Action_Plan_Type
      )
   is
      procedure Attack (Who : Roles.Role_Pointer)
      is
         self   : Hero_Type renames this.Hero.Prototype.all;
         enemy  : Hero_Type renames Who.Prototype.all;
         item   : Item_Type renames All_World.Items(Plan.Item);
         skill  : Int16_t renames self.Skills(s_Throw);
         rate   : Int16_t := (if skill < 30 then 30 else skill);
         damage : Int16_t := Item.HP_Bonus.Now * rate / 100 - 1; 
      begin
         Trace_Log(To_UTF8_Name(self.Name) & " throw " & To_UTF8_Name(Item.Name) & " to " & To_UTF8_Name(enemy.Name));
         Add(enemy.HP, damage);
         Add(enemy.Weakness, Item.WP_Bonus, Degree_Range);
         this.Effects.Points.Clear;
         this.Effects.Points.Append(Plan.Target);
         this.Play_Plan_Movie(ak_Throw, this.Hero.Face, Item.Attack_Effect);
         who.Emit(damage'Img, Colors.Red);
         Trace_Log(To_UTF8_Name(enemy.Name) & StrRes.Hero_State.HP & damage'Img); 
      end Attack;

      function Found_Target (Who : Roles.Role_Pointer) return Boolean
      is
      begin
         if Who.Position /= Plan.Target then 
            return False;
         end if;
         if this.Is_Same_Camp(Who, this.Hero) then
            return False;
         end if;
         Attack(Who);
         return True;
      end Found_Target;
   begin
      this.Iterate_Heroes(Found_Target'Access);
   end Execute_Throw_Plan;

   procedure Execute_Drug_Plan (
      this : in out War_Field_Type;
      Plan : Action_Plan_Type
      )
   is
      State : War_State_Ref := this.Current_Hero_State;
      Item : Item_Type renames All_World.Items(Plan.Drug);
      Self : Hero_Type renames this.Hero.Prototype.all;

      procedure Dec (X : in out Int16_t)
      is
      begin
         X := X - 1;
         Trace_Log("remain count " & x'Img);
      end Dec;
   begin
      Trace_Log(To_UTF8_Name(Self.Name) & " eat " & To_UTF8_Name(Item.Name));
      Apply_Item_Bonus(Self, Item);
      case State.Camp is
         when k_Player =>
            for g of All_World.Leader.Bag loop
               if g.Id = Item.Id and then g.Count > 0 then
                  Dec(g.Count);
                  exit;
               end if;
            end loop;
         when k_Enemy =>
            for i in Self.Items'Range loop
               if Self.Items(i) = Item.Id and then Self.Items_Count(i) > 0 then
                  Dec(Self.Items_Count(i));
                  exit;
               end if;
            end loop;
         when others => 
            null;
      end case;
      this.Hero.Emit(To_UTF8_Name(Item.Name), Colors.White);
   end Execute_Drug_Plan;

   procedure Execute_Cure_Plan (
      this : in out War_Field_Type;
      Plan : Action_Plan_Type
      )
   is
      procedure Cure (Who : Roles.Role_Pointer)
      is
         self   : Hero_Type renames this.Hero.Prototype.all;
         friend : Hero_Type renames Who.Prototype.all;
         skill  : Int16_t renames self.Skills(s_Cure);
         bonus  : Int16_t;
      begin
         Apply_Cure(self, friend, bonus);
         this.Effects.Points.Clear;
         this.Effects.Points.Append(Plan.Target);
         this.Play_Plan_Movie(ak_Throw, this.Hero.Face, 0);
         who.Emit("+" & bonus'Img, Colors.Yellow);
         Trace_Log(To_UTF8_Name(friend.Name) & StrRes.Hero_State.HP & "+" & bonus'Img); 
      end Cure;

      function Found_Target (Who : Roles.Role_Pointer) return Boolean
      is
      begin
         if Who.Position /= Plan.Target then 
            return False;
         end if;
         if not this.Is_Same_Camp(Who, this.Hero) then
            return False;
         end if;
         Cure(Who);
         return True;
      end Found_Target;
   begin
      this.Iterate_Heroes(Found_Target'Access);
   end Execute_Cure_Plan;

   procedure Execute_Poison_Plan (
      this : in out War_Field_Type;
      Plan : Action_Plan_Type
      )
   is
      procedure Poison (Who : Roles.Role_Pointer)
      is
         self   : Hero_Type renames this.Hero.Prototype.all;
         enemy  : Hero_Type renames Who.Prototype.all;
         skill  : Int16_t renames self.Skills(s_Poison);
         resist : Int16_t renames enemy.Skills(s_Resistence);
         weak   : Int16_t;
      begin
         Trace_Log(To_UTF8_Name(self.Name) & " poison " & To_UTF8_Name(enemy.Name));
         weak := (if resist < skill then (skill - resist) / 10 + 1 else 0);
         Add(enemy.Weakness, weak, Degree_Range);
         this.Effects.Points.Clear;
         this.Effects.Points.Append(Plan.Target);
         this.Play_Plan_Movie(ak_Throw, this.Hero.Face, 30);
         who.Emit("+" & weak'Img, Colors.Green);
         Trace_Log(To_UTF8_Name(enemy.Name) & StrRes.Hero_State.Poison & "+" & weak'Img); 
      end Poison;

      function Found_Target (Who : Roles.Role_Pointer) return Boolean
      is
      begin
         if Who.Position /= Plan.Target then 
            return False;
         end if;
         if this.Is_Same_Camp(Who, this.Hero) then
            return False;
         end if;
         Poison(Who);
         return True;
      end Found_Target;
   begin
      this.Iterate_Heroes(Found_Target'Access);
   end Execute_Poison_Plan;

   procedure Execute_Detoxify_Plan (
      this : in out War_Field_Type;
      Plan : Action_Plan_Type
      )
   is
      procedure Detoxify (Who : Roles.Role_Pointer)
      is
         self   : Hero_Type renames this.Hero.Prototype.all;
         friend : Hero_Type renames Who.Prototype.all;
         skill  : Int16_t renames self.Skills(s_Detoxify);
         bonus  : Int16_t;
      begin
         Apply_Detoxify(self, friend, bonus);
         this.Effects.Points.Clear;
         this.Effects.Points.Append(Plan.Target);
         this.Play_Plan_Movie(ak_Throw, this.Hero.Face, 35);
         who.Emit(bonus'Img, Colors.Green);
         Trace_Log(To_UTF8_Name(friend.Name) & StrRes.Hero_State.Poison & bonus'Img); 
      end Detoxify;

      function Found_Target (Who : Roles.Role_Pointer) return Boolean
      is
      begin
         if Who.Position /= Plan.Target then 
            return False;
         end if;
         if not this.Is_Same_Camp(Who, this.Hero) then
            return False;
         end if;
         Detoxify(Who);
         return True;
      end Found_Target;
   begin
      this.Iterate_Heroes(Found_Target'Access);
   end Execute_Detoxify_Plan;

   procedure Execute_Plan (
      this : in out War_Field_Type; 
      Plan : Action_Plan_Type
      )
   is
   begin
      case Plan.Action is
         when wa_Kongfu   => this.Execute_Kongfu_Plan(Plan);
         when wa_Throw    => this.Execute_Throw_Plan(Plan);
         when wa_Cure     => this.Execute_Cure_Plan(Plan);
         when wa_Poison   => this.Execute_Poison_Plan(Plan);
         when wa_Detoxify => this.Execute_Detoxify_Plan(Plan);
         when wa_Drug     => this.Execute_Drug_Plan(Plan);
         when others      => null;
      end case;
      this.Delete_Dead_Heroes;
      this.End_Turn(Plan.Action);
   end Execute_Plan;

   procedure Search_Nearest_Enemy (
      this : in out War_Field_Type;
      Enemy : out Roles.Role_Pointer
      )
   is
      min_trip : Int16_t := Int16_t'Last;

      function Compare (Who : Roles.Role_Pointer) return Boolean
      is
         trip : Int16_t;
      begin
         if this.Is_Same_Camp(Who, this.Hero) then
            return False;
         end if;
         trip := Get_Steps(Who.Position - this.Hero.Position);
         if trip < min_trip then
            min_trip := trip;
            Enemy := Who;
         end if;
         return False;
      end Compare;
   begin
      this.Iterate_Heroes(Compare'Access);
   end Search_Nearest_Enemy;

   procedure Search_Weakest_Enemy (
      this : in out War_Field_Type;
      Enemy : out Roles.Role_Pointer
      )
   is
      min_hp : Int16_t := Int16_t'Last;
      function Compare (Who : Roles.Role_Pointer) return Boolean
      is
         hp : Int16_t;
      begin
         if this.Is_Same_Camp(Who, this.Hero) then
            return False;
         end if;
         hp := Who.Prototype.HP.Now;
         if hp < min_hp then
            min_hp := hp;
            Enemy := Who;
         end if;
         return False;
      end Compare;
   begin
      this.Iterate_Heroes(Compare'Access);
   end Search_Weakest_Enemy;

   procedure Move_Towards (
      this : in out War_Field_Type;
      Where : Point_Type
      )
   is
      points   : Point_Lists.List;
      from     : Point_Type := this.Hero.Position;
      to       : Point_Type := from;
      trip     : Int16_t;
      min_trip : Int16_t := Get_Steps(Where - from);
   begin
      Generate_Movement_Grids(this, points, from, this.Current_Hero_State.Steps);
      for p of points loop
         trip := Get_Steps(p - Where);
         if trip < min_trip then
            min_trip := trip;
            to := p;
         end if;
      end loop;
      if to /= from then
         this.Hero.Move_To(to);
         Trace_Log("move to " & Traces.Image(to));
      end if;
   end Move_Towards;

   function Random_Select (Min, Now, Max : Int16_t) return Boolean
   is
   begin
      -- if now near min then more possible return true
      if now > Min + (Max - Min) * 3 / 4 then
         return False;
      end if;
      return Random_Int16(Rnd_Generator, Min, Max) > Now;
   end Random_Select;

   procedure Auto_Cure (this : in out War_Field_Type)
   is
      self : Hero_Type renames this.Hero.Prototype.all;
      plan : Action_Plan_Type(wa_Cure);
      cure_radius : Int16_t := Get_Radius(self, s_Cure);

      function Need_Cure (Who : Roles.Role_Pointer) return Boolean
      is
         hero : Hero_Type renames Who.Prototype.all;
      begin
         if not this.Is_Same_Camp(Who, this.Hero) then
            return False;
         end if;
         if Get_Steps(who.Position - this.Hero.Position) > cure_radius then
            return False;
         end if;
         if hero.HP.Now > hero.HP.Max / 2 then
            return False;
         end if;
         if not Random_Select(0, hero.HP.Now, hero.HP.Max) then
            return False;
         end if;
         plan.Target := Who.Position;
         plan.Role := Who;
         this.Execute_Plan(plan);
         return True;
      end Need_Cure;
   begin
      if self.Stamina <= 50 then
         return;
      end if;
      if cure_radius < 1 then
         return;
      end if;
      this.Iterate_Heroes(Need_Cure'Access);
   end Auto_Cure;

   procedure Auto_Drug (this : in out War_Field_Type)
   is
      type Drug_Target is (dt_HP, dt_MP, dt_SP, dt_WP);
      subtype Drug_Plan is Action_Plan_Type(wa_Drug);
      self : Hero_Type renames this.Hero.Prototype.all;
      state : War_State_Ref := this.Current_Hero_State;
      Plans : array(Drug_Target) of Drug_Plan;
      Effects : array(Drug_Target) of Bounded_State_Type := (
         Self.HP, Self.MP, (Self.Stamina, 100), (-Self.Weakness, 0));
      -- random rate
      Randoms : array(Drug_Target) of Boolean := (
         Random_Select(0, Self.HP.Now, Self.HP.Max),
         Random_Select(0, Self.MP.Now, Self.MP.Max),
         Random_Select(0, Self.Stamina, 100),
         Random_Select(-100, -Self.Weakness, 0));

      procedure Search_Bag (Target : Drug_Target)
      is
         diff : Int16_t := 1000;
         Expect_Effect : Int16_t := Effects(Target).Max - Effects(Target).Now;

         procedure Compare_Effect (Item : Item_Type)
         is
            procedure Test (Drug_Effect : Int16_t)
            is
               new_diff : Int16_t;
            begin
               if Drug_Effect = 0 then
                  return;
               end if;
               new_diff := abs(Drug_Effect - Expect_Effect);
               if new_diff < diff then
                  new_diff := diff;
                  Plans(Target).Drug := Item.ID;
               end if;
            end Test;
         begin
            if Item.Kind /= ik_Drug_Item then
               return;
            end if;
            case Target is
               when dt_HP => Test(Item.HP_Bonus.Now);
               when dt_MP => Test(Item.MP_Bonus.Now);
               when dt_SP => Test(Item.SP_Bonus);
               when dt_WP => Test(-Item.WP_Bonus);
            end case;
         end Compare_Effect;
      begin
         if Expect_Effect = 0 or else not Randoms(target) then
            return;
         end if;
         case state.Camp is
            when k_Enemy =>
               for i in self.Items'Range loop
                  exit when diff = 0;
                  if self.Items(i) /= No_Item then 
                     if self.Items_Count(i) > 0 then
                        Compare_Effect(All_World.Items(self.Items(i)));
                     end if;
                  end if;
               end loop;
            when k_Player =>
               for g of All_World.Leader.Bag loop
                  exit when diff = 0;
                  if g.Id /= No_Item and then g.Count > 0 then
                     Compare_Effect(All_World.Items(g.Id));
                  end if;
               end loop;
            when others =>
               null;
         end case;
      end Search_Bag;
   begin
      for p of Plans loop
         p := (
            Action => wa_Drug, 
            Target => this.Hero.Position, 
            Role => this.Hero, 
            Drug => No_Item);
      end loop;
      for target in Drug_Target loop
         Search_Bag(target);
         if Plans(target).Drug /= No_Item then
            this.Execute_Plan(Plans(target));
            exit;
         end if;
      end loop;
   end Auto_Drug;

   procedure Auto_Attack (this : in out War_Field_Type)
   is
      -- select kongfu
      turn_state : War_State_Ref := this.Current_Hero_State;
      max_damage : Int16_t := 0;
      plan       : Kongfu_Plan_Type;
      enemy      : Roles.Role_Pointer;
      offset     : Point_Type;
      trip       : Int16_t;
   begin
      if turn_state.Camp = k_Enemy then
         this.Search_Nearest_Enemy(enemy);
         if enemy = null then
            return;
         else
            this.Move_Towards(enemy.Position);
         end if;
      else
         this.Search_Weakest_Enemy(enemy);
         if enemy /= null then
            this.Move_Towards(enemy.Position);
         else
            this.Search_Nearest_Enemy(enemy);
            if enemy = null then
               return;
            else
               this.Move_Towards(enemy.Position);
            end if;
         end if;
      end if;
      offset := enemy.Position - this.Hero.Position;
      trip   := Get_Steps(offset);
      for plan_i of turn_state.Kongfus loop
         declare
            kongfu : Kongfu_Type renames All_World.Kongfus(plan_i.Kongfu);
            cr     : Int16_t renames kongfu.Cast_Range(plan_i.Level);
            dr     : Int16_t renames Kongfu.Damage_Range(plan_i.Level);
            damage : Int16_t renames kongfu.Attack(plan_i.Level);
         begin
            -- check attack range
            case kongfu.Range_Type is
               when ar_Point =>
                  if trip <= cr and then max_damage < damage then
                     max_damage := damage;
                     plan := plan_i;
                     plan.Target := enemy.Position;
                     plan.Role := enemy;
                     plan.Face := Offset_To_Face(plan.Target - this.Hero.Position);
                  end if;
               when ar_Line | ar_Cross =>
                  if (offset.x = 0 or offset.y = 0) and then trip < cr then
                     if max_damage < damage then
                        max_damage := damage;
                        plan := plan_i;
                        plan.Target := this.Hero.Position;
                        plan.Role := null;
                        plan.Face := Offset_To_Face(offset);
                     end if;
                  end if;
               when ar_Area =>
                  if trip <= cr + dr and then max_damage < damage then
                     max_damage := damage;
                     if dr = 0 then
                        plan := plan_i;
                        plan.Target := enemy.Position;
                        plan.Role := enemy;
                        plan.Face := Offset_To_Face(plan.Target - this.Hero.Position);
                     else
                        plan := plan_i;
                        plan.Role := null;
                        -- TODO : compute where by enemy position
                        this.Compute_Best_Area_Attack_Point(
                           this.Hero,
                           cr,
                           dr,
                           Plan.Target);
                        plan.Face := Offset_To_Face(plan.Target - this.Hero.Position);
                     end if;
                  end if;
            end case;
         end;
      end loop;
      if max_damage = 0 then
         return;
      end if;
      this.Execute_Plan(plan);
   end Auto_Attack;

   procedure Auto_Fight (this : in out War_Field_Type)
   is
      use type Roles.Role_Pointer;
      -- AI
      -- found target
      -- attack target
      state : War_State_Ref := this.Current_Hero_State;
      self  : Hero_Type renames this.Hero.Prototype.all;
   begin
      loop
         this.Auto_Drug;
         exit when not state.Can_Action;
         exit when self.Stamina <= 10;
         this.Auto_Cure;
         exit when not state.Can_Action;
         this.Auto_Attack;
         exit;
      end loop;
      if state.Can_Action then
         this.End_Turn(wa_Rest);
      end if;
   end Auto_Fight;

   function Select_Location (
      this            : in out War_Field_Type;
      Radius          : Int16_t;
      Result          : in out Point_Type;
      Enable_Obstacle : Boolean := True
      ) return Boolean
   is
      use type Interfaces.C.int;
      ev       : aliased SDL.Events.Event_t;
      moved    : Boolean := False;
      new_loc  : Point_Type;
   begin
      this.Shadows.Active := True;
      this.Locator.Active := True;
      this.Reset_Shadows(this.Hero.Position, Radius, Enable_Obstacle);
      while not moved loop
         exit when SDL.Events.Wait_Event(ev'Unchecked_Access) = 0;
         new_loc := this.Locator.Position;
         case ev.Ev_Type is
            when SDL.Events.Key_Up =>
               case ev.Key.Keysym.Sym is
                  when SDL.Keysym.K_Escape =>
                     exit;
                  when SDL.Keysym.K_Up    => 
                     Move_By_Face(new_loc, North);
                  when SDL.Keysym.K_Down  => 
                     Move_By_Face(new_loc, South);
                  when SDL.Keysym.K_Left  => 
                     Move_By_Face(new_loc, West);
                  when SDL.Keysym.K_Right => 
                     Move_By_Face(new_loc, East);
                  when SDL.Keysym.K_Space | SDL.Keysym.K_Return =>
                     Result := new_loc;
                     moved := True;
                  when others => null;
               end case;
            when SDL.Events.Quit =>
               this.Close;
            when others =>
               null;
         end case;
         -- redraw locator
         if new_loc /= this.Locator.Position then
            if In_Shadows(new_loc, this.Shadows) then
               this.Locator.Position := new_loc;
               this.Canvas.Invalidate;
               this.Canvas.Repaint;
            end if;
         end if;
      end loop;
      this.Shadows.Active := False;
      this.Locator.Active := False;
      return moved;
   end Select_Location;

   function Select_Attack_Direction (
      this : in out War_Field_Type;
      Face : in out Face_Type
      ) return Boolean
   is
      use type Interfaces.C.int;
      d : Legend.Dialogs.Dialog_Type;
   begin
      d.Prompt(StrRes.Please_Select_Attack_Direction);
      loop
         d.Run;
         case d.Dialog_Result is
            when SDL.Keysym.K_Up    => Face := North; return True;
            when SDL.Keysym.K_Down  => Face := South; return True;
            when SDL.Keysym.K_Left  => Face := West;  return True;
            when SDL.Keysym.K_Right => Face := East;  return True;
            when others             => return False;
         end case;
      end loop;
   end Select_Attack_Direction;

   function Select_Attack_Kongfu (
      this : in out War_Field_Type;
      Plan : in out Kongfu_Plan_Type
      ) return Boolean
   is
      use type Menus.Menuitem_Pointer;
      self  : Hero_Type renames this.Hero.Prototype.all;
      state : War_State_Ref := this.Current_Hero_State;
      km : Menus.Menubar_Pointer := Menus.Create(this'Unrestricted_Access);
      mi : Menus.Menuitem_Pointer := null;
   begin
      for i in State.Kongfus.First_Index .. State.Kongfus.Last_Index loop
         declare
            kf : Kongfu_Plan_Type renames State.Kongfus(i);
            k : Kongfu_Type renames All_World.Kongfus(kf.Kongfu);
         begin
            if k.MP_Consume <= self.MP.Now then
               mi := new Menus.Menuitem_Type;
               mi.Caption(To_UTF8_Name(k.Name));
               mi.Id(i);
               km.Add_Item(mi);
            end if;
         end;
      end loop;
      km.Run;
      if km.Id >= 0 then
         Plan := State.Kongfus(km.Id);
      else
         Plan.Kongfu := No_Kongfu;
      end if;
      Menus.Delete(km);
      return Plan.Kongfu /= No_Kongfu;
   end Select_Attack_Kongfu;

   procedure On_War_Menu_Move (this : in out War_Field_Type)
   is
      use type Interfaces.C.int;
      who      : War_State_Ref := this.Current_Hero_State;
      new_loc  : Point_Type := this.Hero.Position;
   begin
      loop
         exit when not this.Select_Location(who.Steps, new_loc, False);
         exit when new_loc = this.Hero.Position;
         exit when this.Has_Obstacle_At(new_loc, No_Horse);
         who.Steps := who.Steps - Get_Steps(new_loc - this.Hero.Position);
         this.Hero.Move_To(new_loc);
--         this.Hero.Position(new_loc);
--         this.Hero.Focus;
         this.Reset_Shadows(this.Hero.Position, who.Steps, False);
         exit;
      end loop;
   end On_War_Menu_Move;

   procedure On_War_Menu_Cast (this : in out War_Field_Type)
   is
      State : War_State_Ref := this.Current_Hero_State;
      h  : Hero_Type renames this.Hero.Prototype.all;
      plan : Action_Plan_Type(wa_Kongfu);
   begin
      if not this.Select_Attack_Kongfu(plan) then
         return;
      end if;
      plan.Face := this.Hero.Face;
      declare
         kongfu : Kongfu_Type renames All_World.Kongfus(plan.Kongfu);
      begin
         case kongfu.Range_Type is
            when ar_Point | ar_Area =>
               declare
                  r : Int16_t := kongfu.Cast_Range(plan.Level);
               begin
                  if not this.Select_Location(r, plan.Target) then 
                     return;
                  end if;
               end;
            when ar_Line  =>
               plan.Target := this.Hero.Position;
               if not this.Select_Attack_Direction(plan.Face) then
                  return;
               end if;
            when ar_Cross =>
               plan.Target := this.Hero.Position;
         end case;
         this.Execute_Plan(plan);
      end;
   end On_War_Menu_Cast;

   procedure On_War_Menu_Detoxify (this : in out War_Field_Type)
   is
      self : Hero_Type renames this.Hero.Prototype.all;
      plan : Action_Plan_Type(wa_Detoxify);
      r    : Int16_t := Get_Radius(self, s_Detoxify);
   begin
      loop
         exit when not this.Select_Location(r, plan.Target); 
         plan.Role := this.Find_Hero_At(plan.Target);
         exit when plan.Role = null;
         exit when not this.Is_Same_Camp(plan.Role, this.Hero);
         this.Execute_Plan(plan);
         return;
      end loop;
   end On_War_Menu_Detoxify;

   procedure On_War_Menu_Cure (this : in out War_Field_Type)
   is
      self : Hero_Type renames this.Hero.Prototype.all;
      plan : Action_Plan_Type(wa_Cure);
      r    : Int16_t := Get_Radius(self, s_Cure);
   begin
      loop
         exit when not this.Select_Location(r, plan.Target);
         plan.Role := this.Find_Hero_At(plan.Target);
         exit when plan.Role = null;
         exit when not this.Is_Same_Camp(plan.Role, this.Hero); 
         this.Execute_Plan(plan);
         return;
      end loop;
   end On_War_Menu_Cure;

   procedure On_War_Menu_Poison (this : in out War_Field_Type)
   is
      self : Hero_Type renames this.Hero.Prototype.all;
      plan : Action_Plan_Type(wa_Poison);
      r    : Int16_t := Get_Radius(self, s_Poison);
   begin
      loop
         exit when not this.Select_Location(r, plan.Target);
         plan.Role := this.Find_Hero_At(plan.Target);
         exit when plan.Role = null;
         exit when this.Is_Same_Camp(plan.Role, this.Hero); 
         this.Execute_Plan(plan);
         return;
      end loop;
   end On_War_Menu_Poison;

   function War_Item_Filter (Item : Item_Type) return Boolean
   is
   begin
      return Item.Kind in ik_Drug_Item | ik_Throw_Item;
   end War_Item_Filter;

   procedure On_War_Menu_Item (this : in out War_Field_Type)
   is
      d : Items_Dialog.Items_Dialog_Type;
      grid : Item_Grid_Type;
   begin
      d.Setup(Filter => War_Item_Filter'Access);
      d.Run;
      grid := d.Selected;
      if grid.Id = No_Item then
         return;
      end if;
      declare
         item : Item_Type renames All_World.Items(grid.Id);
      begin
         this.Hero.Use_Item(item);
      end;
   end On_War_Menu_Item;

   procedure Use_Drug_Item (this : in out War_Field_Type; Item : in out Item_Type)
   is
      Hero : Hero_Type renames this.Hero.Prototype.all;
      plan : Action_Plan_Type(wa_Drug);
   begin
      plan.Drug := Item.Id;
      plan.Target := this.Hero.Position;
      this.Execute_Plan(plan);
   end Use_Drug_Item;

   procedure Use_Throw_Item (this : in out War_Field_Type; Item : in out Item_Type)
   is
      self : Hero_Type renames this.Hero.Prototype.all;
      plan : Action_Plan_Type(wa_Throw);
      r    : Int16_t := Get_Radius(self, s_Throw);
   begin
      loop
         exit when not this.Select_Location(r, plan.Target);
         plan.Role := this.Find_Hero_At(plan.Target);
         exit when plan.Role = null;
         exit when this.Is_Same_Camp(plan.Role, this.Hero); 
         plan.Item := Item.Id;
         this.Execute_Plan(plan);
         return;
      end loop;
   end Use_Throw_Item;

   procedure On_War_Menu_State (this : in out War_Field_Type)
   is
      d : Hero_State_Dialog.Hero_State_Dialog_Type;
   begin
      d.Run;
   end On_War_Menu_State;

   procedure Initialize (this : in out War_Field_Type)
   is
   begin
      Stage_Type(this).Initialize;
      this.Host := False;
      this.Winner := k_Neutral;
      this.Shadows := (
         Active  => False,
         Picture => Resources.Shadow_Picture,
         others  => <>);
      this.Locator := (
         Active  => False,
         Picture => Resources.Locator_Picture,
         others  => <>);
      this.Effects.Active := False;
      this.Box.Visible := False;
      this.Box.Back_Color := (A => 128, others => 0);
      this.Components.Append(this.Box'Unrestricted_Access);
   end Initialize;

   procedure Finalize (this : in out War_Field_Type)
   is
   begin
      Stage_Type(this).Finalize;
   end Finalize;

   procedure Run (this : in out War_Field_Type)
   is
   begin
      while not this.Heroes.Is_Empty loop
         this.Hero := this.Heroes.First_Element;
         exit when not this.Next_Turn;
         this.Heroes.Delete_First;
         this.Heroes.Append(this.Hero);
      end loop;
      if this.Winner = k_Enemy then
         this.Say(StrRes.Battle_Fail);
         return;
      end if;
      this.Say(StrRes.Battle_Win);
      declare
         Exp_Per_Winner : Uint16_t;
      begin
         Exp_Per_Winner := this.Profile.Exp / Uint16_t(this.Heroes.Length);
         for h of this.Heroes.all loop
            declare
               hero : Hero_Type renames All_World.Heroes(h.Prototype.Id);
               state : War_State_Ref := this.States.Reference(hero.Id);
            begin
               state.Exp := state.Exp + Exp_Per_Winner;
            end;
         end loop;
         for i in this.States.Iterate loop
            declare
               hero : Hero_Type renames All_World.Heroes(Role_State_Maps.Key(i));
               state : War_State_Ref := this.States.Reference(i);
            begin
               if state.Camp = k_Player then
                  this.Say(To_UTF8_Name(hero.Name) & StrRes.Gain_Exp & state.Exp'Img);
                  this.Inc_Exp(hero, state.Exp);
               end if;
            end;
         end loop;
      end;
   end Run;

   function More_Quick (
      Left, Right : Roles.Role_Pointer
      ) return Boolean
   is
   begin
      return Left.Prototype.Skills(s_Movement) > Right.Prototype.Skills(s_Movement);
   end More_Quick;

   procedure Generate_Movement_Grids(
      Points : in out Point_Lists.List;
      Center : Point_Type;
      Radius : Int16_t
      )
   is
      k : Int16_t;
   begin
      Points.Clear;
      for i in -Radius .. Radius loop
         k := Radius - abs(i);
         for j in -k .. k loop
            Points.Append(Center + (j, i));
         end loop;
      end loop;
   end Generate_Movement_Grids;

   procedure Generate_Area_Attack_Grids (
      Points : in out Point_Lists.List;
      Center : Point_Type;
      Radius : Int16_t
      )
   is
   begin
      for i in -Radius .. Radius - 1 loop
         for j in -Radius .. Radius - 1 loop
            Points.Append(Center + (j, i));
         end loop;
      end loop;
   end Generate_Area_Attack_Grids;

   function In_Shadows (
      Point   : Point_Type; 
      Shadows : Shadows_Type
      ) return Boolean
   is
   begin
      return Shadows.Points.Contains(Point);
   end In_Shadows;

   procedure Init_Turn (
      this : in out War_Field_Type
      )
   is
      State : War_State_Ref := this.Current_Hero_State;
      Hero : Hero_Type renames this.Hero.Prototype.all;
      plan : Kongfu_Plan_Type;
   begin
      Trace_Log("turn : " & To_UTF8_Name(Hero.Name));
      State.Steps := Get_Radius(Hero, s_Movement);
      State.Can_Action := True;
      Sub(Hero.HP, Hero.Weakness / 10);
      -- get available kongfus
      State.Kongfus.Clear;
      for i in Hero.Kongfu'Range loop
         plan.Kongfu := Hero.Kongfu(i);
         plan.Index  := i;
         if plan.Kongfu in 0 | No_Kongfu then
            goto continue;
         end if;
         plan.Level := Get_Kongfu_Level(Hero, i);
         declare
            kongfu : Kongfu_Type renames All_World.Kongfus(plan.Kongfu);
         begin
            if kongfu.MP_Consume <= Hero.MP.Now then
               for i in reverse 1 .. plan.Level loop
                  if Kongfu.MP_Consume * Int16_t(i) <= Hero.MP.Now then
                     plan.Level := i;
                     exit;
                  end if;
               end loop;
               state.Kongfus.Append(plan);
            end if;
         end;
         <<continue>>
      end loop;
      this.Update_Camp_Knowdges;
--      for h of this.Heroes.all loop
--         h.Emit(To_UTF8_Name(h.Prototype.Name), Colors.White);
--      end loop;
   end Init_Turn;


   procedure End_Turn (
      this : in out War_Field_Type;
      Action : War_Action_Type := wa_Rest
      )
   is
      State : War_State_Ref := this.Current_Hero_State;
      Hero : Hero_Type renames this.Hero.Prototype.all;
   begin
      State.Steps := 0;
      State.Can_Action := False;
      case Action is 
         when wa_Rest =>
            Add(Hero.Stamina, 3, Degree_Range);
            Trace_Log(To_UTF8_Name(Hero.Name) & " take rest");
            Add(Hero.HP, 3);
            Add(Hero.MP, 3);
         when wa_Drug =>
            null;
         when others =>
            Add(Hero.Stamina, -3, Degree_Range);
      end case;
   end End_Turn;

   procedure On_Popup_Menu (
      this : in out War_Field_Type;
      Menubar : in out Menus.Menubar_Pointer
      )
   is
      use Menus;
      use Context_Menus;
      who  : Role_State_Maps.Reference_Type := this.Current_Hero_State;
      hero : Hero_Type renames this.Hero.Prototype.all;

      mainmenu : Menubar_Pointer := Menus.Create(this'Unrestricted_Access);

      procedure Append (Caption : UTF8_String; Id : Integer)
      is
         mi : Menuitem_Pointer := new Menuitem_Type;
      begin
         mi.Caption(Caption);
         mi.Id(Id);
         mainmenu.Add_Item(mi);
      end Append;
   begin
      ---------------------------------
      if who.Steps > 0 then
         Append(StrRes.War_Menus.Move, CMD_Move);
      end if;
      if not who.Kongfus.Is_Empty then
         Append(StrRes.War_Menus.Attack, CMD_Cast);
      end if;
      if hero.Skills(s_Poison) > 10 then
         Append(StrRes.War_Menus.Poison, CMD_Poison);
      end if;
      if hero.Skills(s_Detoxify) > 10 then
         Append(StrRes.War_Menus.Jiedu, CMD_Detoxify);
      end if;
      if hero.Skills(s_Cure) > 10 then
         Append(StrRes.War_Menus.Cure, CMD_Cure);
      end if;
      Append(StrRes.War_Menus.Item, CMD_Item);
      Append(StrRes.War_Menus.Wait, CMD_Wait);
      Append(StrRes.War_Menus.State, CMD_State);
      Append(StrRes.War_Menus.Rest, CMD_Rest);
      Append(StrRes.War_Menus.Auto, CMD_Auto);
      Menubar := mainmenu;
   end On_Popup_Menu;


end Legend.Stages.WarFields;
